package velox.jcr.mapper;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;


public class DefaultUnitOfWork implements UnitOfWork {
	private static final Logger logger = LoggerFactory.getLogger(DefaultUnitOfWork.class);
	
	private SecondLevelCache secondLevelCache;
	private MapperManager mapperManager;
	private Filter filter;
	
	private Map<String, Object> primaryCache;
	private List<TransactionLogEntry> transactionLog = new ArrayList<TransactionLogEntry>();
	
	DefaultUnitOfWork(MapperManager mapperManager, SecondLevelCache secondLevelCache, Filter filter) {
		this.mapperManager = mapperManager;
		this.secondLevelCache = secondLevelCache;
		this.filter = filter;
		primaryCache = new HashMap<String, Object>();
	}

	synchronized void invalidateSecondLevelCache() {
		if (! transactionLog.isEmpty()) {
			logger.debug("Invalidating SLC");
			secondLevelCache.invalidateAux();
			for (TransactionLogEntry entry : transactionLog) {
				secondLevelCache.invalidateEntity(entry.getKey());
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	public void registerLoad(String key, Object obj, Mapper<?> mapper) {
		primaryCache.put(key, obj);
		if (isReadOnly() && mapper instanceof CachingMapper<?, ?>) {
			Object objectToCache = ((CachingMapper) mapper).serialize(obj);
			if (objectToCache != null) {
				secondLevelCache.put(key, objectToCache, CacheType.ENTITY);
			}
		}
	}

	public void registerPersist(String key, Object obj) {
		assert ! isReadOnly();
		primaryCache.put(key, obj);
		transactionLog.add(new TransactionLogEntry(TransactionLogEntry.Type.PERSIST, key));
	}

	public void registerUpdate(String key, Object obj) {
		assert ! isReadOnly();
		primaryCache.put(key, obj);
		transactionLog.add(new TransactionLogEntry(TransactionLogEntry.Type.UPDATE, key));
	}

	public void registerRemove(String key) {
		assert ! isReadOnly();
		primaryCache.remove(key);
		transactionLog.add(new TransactionLogEntry(TransactionLogEntry.Type.REMOVE, key));
	}
	
	@SuppressWarnings("unchecked")
	public <T> T get(String key, Mapper<T> mapper) {
		Object o = primaryCache.get(key);
		if (o == null && isReadOnly()) {
			o = secondLevelCache.get(key, CacheType.ENTITY);
			if (o != null) {
				o = ((CachingMapper) mapper).deserialize(o);
				primaryCache.put(key, o);
			}
		}
		return (T) o;
	}
	
	private boolean isReadOnly() {
		return TransactionSynchronizationManager.isCurrentTransactionReadOnly();
	}
	
	public static class TransactionLogEntry {
		enum Type { PERSIST, REMOVE, UPDATE }

		private Type type;
		private String key;
		
		public TransactionLogEntry(Type type, String key) {
			this.type = type;
			this.key = key;
		}
		
		public Type getType() {
			return type;
		}
		
		public String getKey() {
			return key;
		}
	}

	public SecondLevelCache getSecondLevelCache() {
		return this.secondLevelCache;
	}

	public MapperManager getMapperManager() {
		return this.mapperManager;
	}
	
	public Filter getFilter() {
		return filter;
	}
	
	public void setFilter(Filter filter) {
		this.filter = filter;
	}
}
